package com.cheche.dn.common.base;

import com.cheche.dn.common.Filter;
import com.cheche.dn.common.Order;
import com.cheche.dn.common.Page;
import com.cheche.dn.common.Pageable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Service - 基础
 * Created by cheshun on 15/8/19.
 */
@org.springframework.stereotype.Service
public class Service {
    private static Logger logger = LoggerFactory.getLogger(Service.class);

    /** 更新忽略属性 */
    private static final String[] UPDATE_IGNORE_PROPERTIES = new String[] {
            BaseEntity.ID_PROPERTY_NAME, BaseEntity.CREATE_DATE_PROPERTY_NAME, BaseEntity.MODIFY_DATE_PROPERTY_NAME
    };

    @Autowired
    protected Dao dao;

    private <T extends IdEntity> T setStringId(T entity) {
        if (entity != null) {
            entity.set_id(entity.getId().toString());
        }
        return entity;
    }

    private <T extends IdEntity> List<T> setStringIds(List<T> entities) {
        for (T entity : entities) {
            setStringId(entity);
        }
        return entities;
    }

    /**
     * 查找实体对象
     *
     * @param id
     *            ID
     * @return 实体对象，若不存在则返回null
     */
    public <T extends IdEntity> T find(Class<T> entityClass, Long id) {
        return setStringId(dao.find(entityClass, id));
    }

    /**
     * 查找实体对象
     *
     * @param filters
     * 			  筛选
     * @return 实体第一个对象，若不存在则返回null
     */
    public <T extends IdEntity> T find(Class<T> entityClass, Filter... filters) {
        return setStringId(dao.find(entityClass, filters));
    }

    /**
     * 查找所有实体对象集合
     *
     * @return 所有实体对象集合
     */
    public <T extends IdEntity> List<T> findAll(Class<T> entityClass) {
        return findList(entityClass, null, null);
    }

    /**
     * 查找实体对象集合
     *
     * @param ids
     *            ID
     * @return 实体对象集合
     */
    public <T extends IdEntity> List<T> findList(Class<T> entityClass, Long... ids) {
        List<T> result = Lists.newArrayList();
        if (ids != null) {
            for (Long id : ids) {
                T entity = find(entityClass, id);
                if (entity != null) {
                    result.add(entity);
                }
            }
        }
        return result;
    }

    /**
     * 查找实体对象集合
     *
     * @param filters
     *            筛选
     * @param orders
     *            排序
     * @return 实体对象集合
     */
    public <T extends IdEntity> List<T> findList(Class<T> entityClass, List<Filter> filters, List<Order> orders) {
        return findList(entityClass, null, null, filters, orders);
    }

    /**
     * 查找实体对象集合
     *
     * @param count
     *            数量
     * @param filters
     *            筛选
     * @param orders
     *            排序
     * @return 实体对象集合
     */
    public <T extends IdEntity> List<T> findList(Class<T> entityClass, Integer count, List<Filter> filters, List<Order> orders) {
        return findList(entityClass, null, count, filters, orders);
    }

    /**
     * 查找实体对象集合
     *
     * @param pageable
     *              分页信息
     * @return 实体对象集合
     */
    public <T extends IdEntity> List<T> findList(Class<T> entityClass, Pageable pageable) {
        return findList(entityClass, (pageable.getPageNumber() - 1) * pageable.getPageSize(), pageable.getPageSize(), pageable.getFilters(), pageable.getOrders());
    }

    /**
     * 查找实体对象集合
     *
     * @param first
     *            起始记录
     * @param count
     *            数量
     * @param filters
     *            筛选
     * @param orders
     *            排序
     * @return 实体对象集合
     */
    public <T extends IdEntity> List<T> findList(Class<T> entityClass, Integer first, Integer count, List<Filter> filters, List<Order> orders) {
        return setStringIds(dao.findList(entityClass, first, count, filters, orders));
    }

    /**
     * 查找实体对象分页
     *
     * @param pageable
     *            分页信息
     * @return 实体对象分页
     */
    public <T extends IdEntity> Page<T> findPage(Class<T> entityClass, Pageable pageable) {
        Page<T> page = dao.findPage(entityClass, pageable);
        List<T> list = page.getList();
        setStringIds(list);
        return page;
    }

    /**
     * 查询实体对象总数
     *
     * @return 实体对象总数
     */
    public <T extends IdEntity> long count(Class<T> entityClass) {
        return count(entityClass, new Filter[]{});
    }

    /**
     * 查询实体对象数量
     *
     * @param filters
     *            筛选
     * @return 实体对象数量
     */
    public <T extends IdEntity> long count(Class<T> entityClass, Filter... filters) {
        return dao.count(entityClass, filters);
    }

    /**
     * 对某个字段求和
     *
     * @param sumProperty 字段名
     *
     * @return sum
     */
    public <T extends IdEntity> Number sum(Class<T> entityClass, String sumProperty) {
        return sum(entityClass, sumProperty, new Filter[]{});
    }

    /**
     * 对某个字段求和
     *
     * @param sumProperty 字段名
     *
     * @param filters
     *              筛选
     * @return sum
     */
    public <T extends IdEntity> Number sum(Class<T> entityClass, String sumProperty, Filter... filters) {
        return dao.sum(entityClass, sumProperty, filters);
    }
    /**
     * 判断实体对象是否存在
     *
     * @param id
     *            ID
     * @return 实体对象是否存在
     */
    public <T extends IdEntity> boolean exists(Class<T> entityClass, Long id) {
        return dao.find(entityClass, id) != null;
    }

    /**
     * 判断实体对象是否存在
     *
     * @param filters
     *            筛选
     * @return 实体对象是否存在
     */
    public <T extends IdEntity> boolean exists(Class<T> entityClass, Filter... filters) {
        return dao.count(entityClass, filters) > 0;
    }

    /**
     * 保存实体对象
     *
     * @param entity
     *            实体对象
     */
    @Transactional
    public <T extends IdEntity> void save(T entity) {
        dao.persist(entity);
        setStringId(entity);
    }

    /**
     * 删除实体对象
     *
     * @param id
     *            ID
     */
    @Transactional
    public <T extends IdEntity> void delete(Class<T> entityClass, Long id) {
        delete(dao.find(entityClass, id));
    }

    /**
     * 删除实体对象
     *
     * @param ids
     *            ID
     */
    @Transactional
    public <T extends IdEntity> void delete(Class<T> entityClass, Long... ids) {
        if (ids != null) {
            for (Long id : ids) {
                delete(dao.find(entityClass, id));
            }
        }
    }

    /**
     * 删除实体对象
     *
     * @param entity
     *            实体对象
     */
    @Transactional
    public <T extends IdEntity> void delete(T entity) {
        dao.remove(entity);
    }

    /**
     * 更新实体对象
     *
     * @param entity
     *            实体对象
     * @return 实体对象
     */
    @Transactional
    public <T extends IdEntity> T update(T entity) {
        return setStringId(dao.merge(entity));
    }

    /**
     * 条件更新
     *
     * @param updateProperties
     *              需要更新的属性
     * @param filters
     *              筛选
     * @return int
     */
    @Transactional
    public <T extends IdEntity> T conditionUpdate(T entity, String[] updateProperties, Filter... filters) {
        List<Filter> filterList = Lists.newArrayList(filters);
        filterList.add(Filter.eq(IdEntity.ID_PROPERTY_NAME, entity.getId()));
        Map<String, Object> updateParams = getUpdateParams(entity, updateProperties);
        int flag = dao.conditionUpdate(entity.getClass(), updateParams, filterList);
        if (flag <= 0 && !dao.isManaged(entity)) {
            logger.error("条件更新失败!!!");
            throw new RuntimeException("can't update entity in conditions");
        }
        //noinspection unchecked
        return setStringId(dao.find((Class<T>) entity.getClass(), entity.getId()));
    }

    /**
     * 更新实体对象
     *
     * @param entity
     *            实体对象
     * @param ignoreProperties
     *            忽略属性
     * @return 实体对象
     */
    @SuppressWarnings("unchecked")
    @Transactional
    public <T extends IdEntity> T ignoreUpdate(T entity, String... ignoreProperties) {
        Assert.notNull(entity);

        if (dao.isManaged(entity)) {
            throw new IllegalArgumentException("Entity must not be managed");
        }
        T persistent = dao.find((Class<T>) entity.getClass(), dao.getIdentifier(entity));
        if (persistent != null) {
            copyProperties(entity, persistent, (String[]) ArrayUtils.addAll(ignoreProperties, UPDATE_IGNORE_PROPERTIES), true);
            return update(persistent);
        } else {
            return update(entity);
        }
    }

    /**
     * 更新实体对象
     *
     * @param entity
     *            实体对象
     * @param appointProperties
     *            指定属性
     * @return 实体对象
     */
    @SuppressWarnings("unchecked")
    @Transactional
    public <T extends IdEntity> T appointUpdate(T entity, String... appointProperties) {
        Assert.notNull(entity);

        if (dao.isManaged(entity)) {
            throw new IllegalArgumentException("Entity must not be managed");
        }
        T persistent = dao.find((Class<T>) entity.getClass(), dao.getIdentifier(entity));
        if (persistent != null) {
            copyProperties(entity, persistent, appointProperties, false);
            return update(persistent);
        } else {
            return update(entity);
        }
    }

    private <T extends IdEntity> Map<String, Object> getUpdateParams(T entity, String[] updateProperties) {
        if (updateProperties == null) return Collections.emptyMap();
        PropertyDescriptor[] entityPds = BeanUtils.getPropertyDescriptors(entity.getClass());
        Map<String, Object> params = Maps.newHashMap();
        for (String property : updateProperties) {
            params.put(property, "");
        }
        for (PropertyDescriptor entityPd : entityPds) {
            Method getter = entityPd.getReadMethod();
            if (params.containsKey(entityPd.getName()) && getter != null) {
                try {
                    Object obj = getter.invoke(entity);
                    params.put(entityPd.getName(), obj);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
        return params;
    }

    private <T extends IdEntity> void copyProperties(T source, T target, String[] properties, boolean isIgnore) throws BeansException {
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(target.getClass());
        List<String> propertyList = (properties != null) ? Arrays.asList(properties) : null;
        for (PropertyDescriptor targetPd : targetPds) {
            if (isIgnore) {
                if (targetPd.getWriteMethod() != null && (properties == null || (!propertyList.contains(targetPd.getName())))) {
                    invoke(source, target, targetPd);
                }
            } else {
                if (targetPd.getWriteMethod() != null && (properties != null && propertyList.contains(targetPd.getName()))) {
                    invoke(source, target, targetPd);
                }
            }

        }
    }

    @SuppressWarnings("unchecked")
    private <T extends IdEntity> void invoke(T source, T target, PropertyDescriptor targetPd) {
        PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
        if (sourcePd != null && sourcePd.getReadMethod() != null) {
            try {
                Method readMethod = sourcePd.getReadMethod();
                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                    readMethod.setAccessible(true);
                }
                Object sourceValue = readMethod.invoke(source);
                Object targetValue = readMethod.invoke(target);
                if (sourceValue != null && targetValue != null && targetValue instanceof Collection) {
                    Collection collection = (Collection) targetValue;
                    collection.clear();
                    collection.addAll((Collection) sourceValue);
                } else {
                    Method writeMethod = targetPd.getWriteMethod();
                    if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                        writeMethod.setAccessible(true);
                    }
                    writeMethod.invoke(target, sourceValue);
                }
            } catch (Throwable ex) {
                throw new FatalBeanException("Could not copy properties from source to target", ex);
            }
        }
    }
}
